# python-resmon - Python tools for Resmon monitoring and the Circonus API
# Copyright (c) 2010 Crown coypright
# 
# This file is part of python-resmon.
# 
# python-resmon is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# python-resmon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with python-resmon.  If not, see <http://www.gnu.org/licenses/>.

from parser import *

class CircularDependency(RuleParserError):
    """A class inherits a chain of other blocks which includes itself."""

def refs_in_block(block):
    """The blocks referenced in a given block"""
    refs = set()
    for definition in block.definitions:
        if isinstance(definition, Reference):
            refs.add(definition.ref)
    return refs


def check_circular_dependencies(block, accepted, stack=[]):
    """Check that there are no circular dependencies in the descendants of block.
    
    accepted is a set of blocks that have already been checked.
    stack is the chain of dependencies that have already been followed to get to block.
    """

    if block in stack:
        i = stack.index(block)
        raise CircularDependency(' -> '.join([s.symbol for s in stack[i:] + [block]]))

    # refs to check
    refs = refs_in_block(block) - accepted 

    stack = stack + [block]
    for ref in refs:
        if ref not in accepted:
            check_circular_dependencies(ref, accepted, stack)
    accepted.add(block)


def check_all_circular_dependencies(blocklist):
    accepted = set()
    for block in blocklist:
        if block not in accepted:
            check_circular_dependencies(block, accepted)


def load(fname):
    ast = parser.parse(open(fname).read())
    symtable = {}
    nodetable = {}
    for block in ast:
        if hasattr(block, 'symbol'):
            symbol = block.symbol
            if symbol in symtable:
                raise RuleParserError("Duplicate definition '%s'" % symbol)
            symtable[symbol] = block
        elif hasattr(block, 'hostname'):
            hostname = block.hostname
            if hostname in nodetable:
                raise RuleParserError("Duplicate host '%s'" % hostname)
            nodetable[hostname] = block
        else:
            raise RuleParserError("Unknown block %r" % block)
    for block in symtable.values() + nodetable.values():
        for definition in block.definitions:
            if hasattr(definition, 'using'):
                symbol = definition.using
                try:
                    ref = symtable[symbol]
                except KeyError:
                    raise RuleParserError("Undefined symbol %s" % symbol)
                else:
                    definition.resolve(ref)

    check_all_circular_dependencies(symtable.values())
        
    return nodetable.values()


if __name__ == '__main__':
    for host in load('rules.cm'):
        print host.hostname
        print "Classes:"
        for c in host.get_classes():
            print "  ", c
        print "Metrics:"
        for m in host.get_metrics():
            print "  ", m
